﻿using System;
using System.Text;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Utilities;

namespace LibraryGuard.Utilities
{
	/// <summary>
	/// Formats name of the output file.
	/// </summary>
	/// <remarks>
	/// <para>Format is the string with code and conditional expressions.</para>
	/// 
	/// <para>Code expression is ${ followed by code and then by closing }.
	/// Code is one or more value(connected with plus sign, +).
	/// Value is number(eg. 123), string(inside double quotes, eg. "string"), boolean value(true, false), variable or function call.
	/// Function call is c-like, eg. <code>function(variable, 123, "string", true)</code>.</para>
	/// 
	/// <para>Conditional expression is $[ followed by (, condition, ) and then by code and closing ].
	/// Condition is single comparison(operators are c-like) between two values, eg. <code>variable == function(var)</code>.</para>
	/// 
	/// <para>Sample:
	/// <code>${Artist}\${Year + " - " + Album}$[(TotalDiscs > 1) "\CD " + Disc]\${Track}. ${SafeName(Title)}.${Extension}</code>
	/// </para>
	/// </remarks>
	public sealed class NameFormatter
		: INameFormatter
	{
		#region Logger
		private static NLog.Logger Logger = NLog.LogManager.GetLogger("Formatter");
		#endregion

		#region Private fields
		private IValue Formatter = null;
		#endregion

		#region Public properties
		#endregion

		#region Constructors
		public NameFormatter(string fmt)
		{
			Validate.Debug(() => fmt, v => v.NotNull().NotEmpty());
			this.FormatString = fmt;
			Logger.Debug("Name formetter '{0}' created.", this.FormatString);
		}
		#endregion

		#region INameFormatter Members
		/// <inheritdoc />
		public string FormatString { get; private set; }

		/// <inheritdoc />
		public void Compile()
		{
			Logger.Trace("Compiling format");
			var parser = new Irony.Parsing.Parser(new Internals.FormatGrammar());
			var result = parser.Parse(this.FormatString);
			if (result.HasErrors())
			{
				StringBuilder errorMsg = new StringBuilder("Cannot interpret code:");
				errorMsg.AppendLine();
				foreach (var msg in result.ParserMessages)
					errorMsg.AppendLine(string.Format("{0} at location {1}:{2}", msg.Message, msg.Location.Line, msg.Location.Position));
				Logger.Warn("Cannot compile format, error:\n" + errorMsg.ToString());
				throw new ArgumentException(errorMsg.ToString(), "Format");
			}
			this.Formatter = (IValue)result.Root.AstNode;
			Logger.Debug("Format compiled");
		}

		/// <inheritdoc />
		public string Format(IDataProvider data)
		{
			Validate.Debug(() => data, v => v.NotNull());
			var str = (string)this.Formatter.GetValue(TypeCode.String, data);
			str = str.Trim();
			while (str.StartsWith("\\") || str.StartsWith("/"))
				str = str.Remove(0, 1);
			return str;
		}
		#endregion
	}
}
